package com.pxst.fi.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.pxst.bank.entity.SysBankCardEntity;
import com.pxst.bank.service.SysBankCardGroupService;
import com.pxst.et.entity.SysEtOrderEntity;
import com.pxst.et.service.SysEtOrderService;
import com.pxst.fi.mapper.TreasuryMapper;
import com.pxst.fi.resp.*;
import com.pxst.fo.sys.entity.FoOrderMainEntity;
import com.pxst.fo.sys.entity.SysFoOrderChildEntity;
import com.pxst.fo.sys.service.FoOrderChildService;
import com.pxst.fx.entity.SysFxOrderChildEntity;
import com.pxst.fx.entity.SysFxOrderEntity;
import com.pxst.fx.service.SysFxOrderChildService;
import com.pxst.merchant.entity.SysMerchantInfo;
import com.pxst.merchant.entity.SysMerchantWalletEntity;
import com.pxst.fi.service.TreasuryService;
import com.pxst.merchant.service.SysMerchantInfoService;
import com.pxst.resp.ApiResp;
import com.pxst.sys.service.SysConfigService;
import com.pxst.sys.service.SysCurrencyConfigService;
import com.pxst.utils.HttpUtil;
import com.pxst.utils.RedisUtils;
import icu.mhb.mybatisplus.plugln.core.JoinLambdaWrapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import static com.pxst.fo.sys.constants.FoConstants.*;

/**
 * @author rj
 * @create 2023-10-11-11:34
 */
@Service
public class TreasuryServiceImpl implements TreasuryService {

    @Autowired
    private SysMerchantInfoService merchantInfoService;

    @Autowired
    private FoOrderChildService foOrderChildService;

    @Autowired
    private SysFxOrderChildService fxOrderChildService;

    @Autowired
    private SysEtOrderService etOrderService;

    @Autowired
    private SysCurrencyConfigService currencyConfigService;

    @Autowired
    private SysBankCardGroupService bankCardGroupService;

    @Autowired
    private SysConfigService configService;

    @Autowired
    private HttpUtil httpUtil;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private TreasuryMapper treasuryMapper;

    private BigDecimal total; // 可用余额的总和

    @Override
    public ApiResp<OverallMerchantBalanceResp> getOverallMerchantBalance(String currency) {
        // 获取summary
        List<MerchantSummary> summaryList = getMerchantSummary(currency);
        // 可用余额总额
        total = summaryList.stream().map(MerchantSummary::getAvailableBalance).reduce(BigDecimal.ZERO, BigDecimal::add);
        // 计算占比添加
        summaryList = summaryList.stream().map(item -> {
            BigDecimal percentage = total.compareTo(BigDecimal.ZERO) > 0
                    ? item.getAvailableBalance().divide(total.multiply(new BigDecimal(100)), BigDecimal.ROUND_CEILING).setScale(6)
                    : BigDecimal.ZERO;
            MerchantSummary merchantSummary = new MerchantSummary();
            BeanUtils.copyProperties(item, merchantSummary);
            merchantSummary.setPercentage(percentage);
            return merchantSummary;
        }).collect(Collectors.toList());

        Map<String, List<MerchantSummary>> summaryMap = getSummaryListMap(summaryList, merchantOtherEntry(summaryList));

        // 获取lastest
        List<LatestRecord> latestRecords = getLatestRecords(currency);

        // 组装返回类
        OverallMerchantBalanceResp overallMerchantBalanceResp = new OverallMerchantBalanceResp();
        overallMerchantBalanceResp.setSummary(summaryMap);
        overallMerchantBalanceResp.setLatest(latestRecords);
        overallMerchantBalanceResp.setTime(new Date());

        return ApiResp.sucess(overallMerchantBalanceResp);
    }

    @Override
    public ApiResp<FiTransactionUsageResp> getFiTransactionUsage(String currency) {
        // 获取每日限制数据
        List<DailyLimit> dailyLimits = getDailyLimit(currency);
        // 计算卡组剩余可使用的日流水量（入）和排序
        dailyLimits  = dailyLimits.stream().map(item -> {
            // 计算卡组剩余可使用的日流水量（入）
            item.setRemaining(item.getLimit().subtract(item.getUsage()).setScale(6));
            // 计算卡群组日流水上限（limit）和已经使用的日流水量（入）的百分比
            item.setUsage(calcPercentage(item.getLimit(), item.getUsage()));
            return item;
        }).sorted(Comparator.comparing(DailyLimit::getUsage, Comparator.reverseOrder()).thenComparing(DailyLimit::getGroupId))
            .collect(Collectors.toList());
        // 分成前5和其它
        Map<String, List<DailyLimit>> dailyLimitMap = getSummaryListMap(dailyLimits, fiDailyLimitOtherEntry(dailyLimits));

        // 获取剩余时间数据
        List<RemainingTime> remainingTimes = getRemainingTimes(currency);
        // 计算卡组剩余可使用的月次数和排序
        remainingTimes = remainingTimes.stream().map(item -> {
            // 计算卡组剩余可使用的月次数
            item.setRemaining(item.getLimit().subtract(item.getUsage()).setScale(6));
            // 计算卡群组月次数（入+出）（limit）和已经使用的月次数的百分比
            item.setUsage(calcPercentage(item.getLimit(), item.getUsage()));
            return item;
        }).sorted(Comparator.comparing(RemainingTime::getUsage, Comparator.reverseOrder()).thenComparing(RemainingTime::getGroupId))
                .collect(Collectors.toList());
        // 分成前5和其它
        Map<String, List<RemainingTime>> remainingTimesMap = getSummaryListMap(remainingTimes, fiRemainingTimeOtherEntry(remainingTimes));

        // 封装返回值参数
        FiSummary fiSummary = new FiSummary();
        fiSummary.setDailyLimit(dailyLimitMap);
        fiSummary.setRemainingTimes(remainingTimesMap);
        fiSummary.setTime(new Date());
        return ApiResp.sucess(fiSummary);
    }

    /**
     * 获取remainingTimes数据
     * @param currency
     * @return
     */
    private List<RemainingTime> getRemainingTimes(String currency) {
        if (currency.toUpperCase().equals("ALL")){
            List<RemainingTime> remainingTimes = new ArrayList<>();
            // 查询全部的币种后查询数据
            List<String> currencyList = currencyConfigService.currencyList();
            for (String item : currencyList) {
                List<RemainingTime> remainingTimeList = treasuryMapper.getRemainTimesCardGroup(item);
                remainingTimes.addAll(remainingTimeList);
            }
            return remainingTimes;
        }
        List<RemainingTime> remainingTimes = treasuryMapper.getRemainTimesCardGroup(currency);
        return remainingTimes;
    }

    /**
     * 封装前五个的数据，并为前五中添加第六个数据，和其它数据
     * @param summaryList 查询的数据列表
     * @param summary 除了前5其它的百分比
     * @param <T> 类型
     * @return
     */
    private <T> Map<String, List<T>> getSummaryListMap(List<T> summaryList, T summary) {
        Map<String, List<T>> summaryMap = new HashMap<>();
        if (summaryList.size() > 5){
            List<T> top5 = summaryList.subList(0, 5);
            top5.add(summary);
            summaryMap.put("others", summaryList.subList(6, summaryList.size()));
            summaryMap.put("top5", top5);
        }else {
            summaryList.add(summary);
            summaryMap.put("others", null);
            summaryMap.put("top5", summaryList);
        }
        return summaryMap;
    }

    /**
     * fi前五之后的Usage百分比和Remaining剩余值的计算，剩余流水限额计算
     * @param list
     * @return
     */
    private DailyLimit fiDailyLimitOtherEntry(List<DailyLimit> list){
        BigDecimal usage = getTop5(list).stream().map(DailyLimit::getUsage).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal limit = getTop5(list).stream().map(DailyLimit::getLimit).reduce(BigDecimal.ZERO, BigDecimal::add);
        DailyLimit dailyLimit = new DailyLimit();
        dailyLimit.setGroupId(null);
        dailyLimit.setGroupName("Others");
        dailyLimit.setUsage(calcPercentage(limit, usage));
        dailyLimit.setRemaining(limit.subtract(usage));
        dailyLimit.setLocale(true);
        dailyLimit.setIsOthers(true);
        return dailyLimit;
    }

    /**
     * 获取集合前5的值
     * @param list
     * @param <T>
     * @return
     */
    private <T> List<T> getTop5(List<T> list) {
        return list.size() > 5 ? list.subList(0, 5) : list;
    }

    /**
     * fi前五之后的Usage百分比和Remaining剩余值的计算，剩余次数计算
     * @param list
     * @return
     */
    private RemainingTime fiRemainingTimeOtherEntry(List<RemainingTime> list){
        BigDecimal usage = getTop5(list).stream().map(RemainingTime::getUsage).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal limit = getTop5(list).stream().map(RemainingTime::getLimit).reduce(BigDecimal.ZERO, BigDecimal::add);
        RemainingTime remainingTime = new RemainingTime();
        remainingTime.setGroupId(null);
        remainingTime.setGroupName("Others");
        remainingTime.setUsage(calcPercentage(limit, usage));
        remainingTime.setRemaining(limit.subtract(usage));
        remainingTime.setLocale(true);
        remainingTime.setIsOthers(true);
        return remainingTime;
    }

    /**
     * 计算limit和usage的百分比
     * @param limit
     * @param usage
     * @return
     */
    private BigDecimal calcPercentage(BigDecimal limit, BigDecimal usage) {
        return limit.compareTo(BigDecimal.ZERO) > 0 ? usage.divide(limit.multiply(new BigDecimal(100)), BigDecimal.ROUND_CEILING).setScale(6) : BigDecimal.ZERO;
    }

    /**
     * 获取每日限额数据
     * @param currency 币别
     * @return
     */
    private List<DailyLimit> getDailyLimit(String currency){
        if (currency.toUpperCase().equals("ALL")){
            List<DailyLimit> dailyLimits = new ArrayList<>();
            // 查询全部的币种后查询数据
            List<String> currencyList = currencyConfigService.currencyList();
            for (String item : currencyList) {
                List<DailyLimit> cardGroups = treasuryMapper.getCardGroup(item);
                // 所有报表统计成 USD 货币
                // 获取汇率
                BigDecimal usd = getUsd(item);
                cardGroups.stream().map(dailyLimit -> {
                    // 转换
                    dailyLimit.setUsage(currencyExchange(item, usd, dailyLimit.getUsage()));
                    return dailyLimit;
                }).collect(Collectors.toList());
                dailyLimits.addAll(cardGroups);
            }
            return dailyLimits;
        }
        List<DailyLimit> dailyLimits = treasuryMapper.getCardGroup(currency);
        return dailyLimits;
    }

    /**
     * 商户前五之后剩余的Percentage百分比计算
     * @param list
     * @return
     */
    private MerchantSummary merchantOtherEntry(List<MerchantSummary> list){
        BigDecimal availableBalance = getTop5(list).stream().map(MerchantSummary::getAvailableBalance).reduce(BigDecimal.ZERO, BigDecimal::add);
        MerchantSummary merchantSummary = new MerchantSummary();
        merchantSummary.setMerchantId(null);
        merchantSummary.setName("Others");
        merchantSummary.setAvailableBalance(availableBalance);
        merchantSummary.setPercentage(total.compareTo(BigDecimal.ZERO) > 0
                ? availableBalance.divide(total.multiply(new BigDecimal(100)))
                : BigDecimal.ZERO);
        merchantSummary.setLocale(true);
        merchantSummary.setIsOthers(true);
        return merchantSummary;
    }

    /**
     * 获取lastest
     * @param currency
     * @return
     */
    private List<LatestRecord> getLatestRecords(String currency) {
        if (currency.toUpperCase().equals("ALL")){
            List<LatestRecord> latestRecordResps = new ArrayList<>();
            // 查询全部的币种后查询数据
            List<String> currencyList = currencyConfigService.currencyList();
            for (String item : currencyList) {
                List<LatestRecord> latestRecords = getLatestRecords(item);
                // 所有报表统计成 USD 货币
                // 获取汇率
                BigDecimal usd = getUsd(item);
                // 汇率转换
                latestRecords = latestRecords.stream().map(latestRecord -> {
                    LatestRecord record = new LatestRecord();
                    BeanUtils.copyProperties(latestRecord, record);
                    BigDecimal requestAmount = record.getRequestAmount();
                    record.setRequestAmount(currencyExchange(item, usd, requestAmount));
                    return record;
                }).collect(Collectors.toList());
                latestRecordResps.addAll(latestRecords);
            }
            latestRecordResps = latestRecordResps.stream()
                    .sorted(Comparator.comparing(LatestRecord::getRequestAmount).reversed())
                    .collect(Collectors.toList());
            return latestRecordResps;
        }
        List<LatestRecord> all = fo();
        all.addAll(fx());
        all.addAll(et());
        all.stream().sorted(Comparator.comparing(LatestRecord::getRequestAmount).reversed()).limit(100);
        return all;
    }
    /**
     * 查询外转表的信息
     * @return
     */
    private List<LatestRecord> et(){
        JoinLambdaWrapper<SysEtOrderEntity> lambdaWrapper = new JoinLambdaWrapper<>(SysEtOrderEntity.class);
        lambdaWrapper.in(SysEtOrderEntity::getStatus, STATUS_PENDING, STATUS_PROCESSING, STATUS_MANUAL_PROCESS, STATUS_UNMATCHED_ACC_NAME, STATUS_AWAITING_APPROVAL)
                .selectAs(et -> {
                    et.addFunAlias(SysEtOrderEntity::getId, LatestRecord::getId);
                    et.add("'ET'", "transType", false);
                    et.add(SysEtOrderEntity::getType);
                    et.add(SysEtOrderEntity::getRequestAmount);
                    et.add(SysEtOrderEntity::getToBank);
                    et.add(SysEtOrderEntity::getToCardNumber);
                    et.add(SysEtOrderEntity::getToMan);
                    et.add("CONCAT('E" + "-', DATE_FORMAT(sys_et_order.created_at, '%d%m%y'), if (sys_et_order.id >= 1000, sys_et_order.id, lpad(sys_et_order.id, 4, '0')))", "altId", false);
                });
        lambdaWrapper.leftJoin(SysBankCardEntity.class, SysBankCardEntity::getId, SysEtOrderEntity::getBankCardId)
                .selectAs(bc -> {
                    bc.add(SysBankCardEntity::getAccountCode);
                })
                .end();
        lambdaWrapper.notExists(
                "SELECT *\n" +
                        "    FROM sys_bank_card\n" +
                        "    INNER JOIN sys_available_bank ON sys_bank_card.available_bank_id = sys_available_bank.id\n" +
                        "    WHERE sys_available_bank.bank_code = sys_et_order.to_bank\n" +
                        "    AND sys_bank_card.bank_card_number = sys_et_order.to_card_number"
        );
        List<LatestRecord> latestRecords = etOrderService.joinList(lambdaWrapper, LatestRecord.class);
        return latestRecords;
    }

    /**
     * 查询子下发表的信息
     * @return
     */
    private List<LatestRecord> fx(){
        JoinLambdaWrapper<SysFxOrderChildEntity> lambdaWrapper = new JoinLambdaWrapper<>(SysFxOrderChildEntity.class);
        lambdaWrapper.in(SysFxOrderChildEntity::getStatus, STATUS_PENDING, STATUS_PROCESSING, STATUS_MANUAL_PROCESS, STATUS_UNMATCHED_ACC_NAME, STATUS_AWAITING_APPROVAL)
                .selectAs(subFx -> {
                    subFx.addFunAlias(SysFxOrderChildEntity::getId, LatestRecord::getId);
                    subFx.add("'FX'", "transType", false);
                    subFx.add(SysFxOrderChildEntity::getRequestAmount);
                    subFx.add("CONCAT('Ss" + "-', DATE_FORMAT(sys_fx_order_child.created_at, '%d%m%y'), if (sys_fx_order_child.id >= 1000, sys_fx_order_child.id, lpad(sys_fx_order_child.id, 4, '0')))", "altId", false);
                });
        lambdaWrapper.leftJoin(SysBankCardEntity.class, SysBankCardEntity::getId, SysFxOrderChildEntity::getFromBankCardId)
                .selectAs(bc -> {
                    bc.add(SysBankCardEntity::getAccountCode);
                }).end();
        lambdaWrapper.leftJoin(SysFxOrderEntity.class, SysFxOrderEntity::getId, SysFxOrderChildEntity::getFxOrderMainId)
                .selectAs(fx -> {
                    fx.add(SysFxOrderEntity::getToBank);
                    fx.add(SysFxOrderEntity::getToCardNumber);
                    fx.add(SysFxOrderEntity::getToMan);
                    fx.add(SysFxOrderEntity::getMerchantId);
                }).end();
        lambdaWrapper.leftJoin(SysMerchantInfo.class, SysMerchantInfo::getId, SysFxOrderEntity::getMerchantId)
                .selectAs(merchant -> {
                    merchant.addFunAlias(SysMerchantInfo::getCode, LatestRecord::getMerchantCode);
                    merchant.addFunAlias(SysMerchantInfo::getName, LatestRecord::getMerchantName);
                })
                .end();
        List<LatestRecord> latestRecords = fxOrderChildService.joinList(lambdaWrapper, LatestRecord.class);
        return latestRecords;
    }

    /**
     * 查询子代付表的信息
     * @return
     */
    private List<LatestRecord> fo(){
        JoinLambdaWrapper<SysFoOrderChildEntity> lambdaWrapper = new JoinLambdaWrapper<>(SysFoOrderChildEntity.class);
        lambdaWrapper.in(SysFoOrderChildEntity::getStatus, STATUS_PENDING, STATUS_PROCESSING, STATUS_MANUAL_PROCESS, STATUS_UNMATCHED_ACC_NAME, STATUS_AWAITING_APPROVAL)
                .selectAs(subFo -> {
                    subFo.addFunAlias(SysFoOrderChildEntity::getId, LatestRecord::getId);
                    subFo.add("'FO'", "transType", false);
                    subFo.add(SysFoOrderChildEntity::getRequestAmount);
                    subFo.add("CONCAT('Ws" + "-', DATE_FORMAT(sys_fo_order_child.created_at, '%d%m%y'), if (sys_fo_order_child.id >= 1000, sys_fo_order_child.id, lpad(sys_fo_order_child.id, 4, '0')))", "altId", false);
                });
        lambdaWrapper.leftJoin(SysBankCardEntity.class, SysBankCardEntity::getId, SysFoOrderChildEntity::getFromBankCardId)
                .selectAs(bc -> {
                    bc.add(SysBankCardEntity::getAccountCode);
                }).end();
        lambdaWrapper.leftJoin(FoOrderMainEntity.class, FoOrderMainEntity::getId, SysFoOrderChildEntity::getFoOrderMainId)
                .selectAs(fo -> {
                    fo.add(FoOrderMainEntity::getToBank);
                    fo.add(FoOrderMainEntity::getToCardNumber);
                    fo.add(FoOrderMainEntity::getToMan);
                    fo.add(FoOrderMainEntity::getMerchantId);
                }).end();
        lambdaWrapper.leftJoin(SysMerchantInfo.class, SysMerchantInfo::getId, FoOrderMainEntity::getMerchantId)
                .selectAs(merchant -> {
                    merchant.addFunAlias(SysMerchantInfo::getCode, LatestRecord::getMerchantCode);
                    merchant.addFunAlias(SysMerchantInfo::getName, LatestRecord::getMerchantName);
                })
                .end();
        List<LatestRecord> latestRecords = foOrderChildService.joinList(lambdaWrapper, LatestRecord.class);
        return latestRecords;
    }

    /**
     * 获取merchantSummary
     * @param currency
     * @return
     */
    private List<MerchantSummary> getMerchantSummary(String currency){
        List<MerchantSummary> merchantSummaries = new ArrayList<>();
        if (currency.toUpperCase().equals("ALL")){
            // 获取全部币种名
            List<String> currencyList = currencyConfigService.currencyList();
            for (String item : currencyList) {
                List<MerchantSummary> merchantSummaryList = getSummaryResps(item);
                // 所有报表统计成 USD 货币
                // 获取汇率
                BigDecimal usd = getUsd(item);
                merchantSummaryList = merchantSummaryList.stream().map(merchantSummary -> {
                    MerchantSummary summary = new MerchantSummary();
                    BeanUtils.copyProperties(merchantSummary, summary);
                    BigDecimal availableBalance = merchantSummary.getAvailableBalance();
                    summary.setAvailableBalance(currencyExchange(item, usd, availableBalance));
                    return summary;
                }).collect(Collectors.toList());
                merchantSummaries.addAll(merchantSummaryList);
            }
            merchantSummaries = merchantSummaries.stream()
                    .sorted(Comparator.comparing(MerchantSummary::getAvailableBalance).reversed())
                    .collect(Collectors.toList());
            return merchantSummaries;
        }
        merchantSummaries = getSummaryResps(currency);
        return merchantSummaries;
    }

    /**
     * 对应货币与USD的转换
     * @param currency 货币
     * @param usd 汇率
     * @param balance 余额
     * @return
     */
    private BigDecimal currencyExchange(String currency, BigDecimal usd, BigDecimal balance) {
        // 先转换为实际货币单位
        Boolean realConversion = StringUtils.hasText(configService.getSubCode("realConversion", currency));
        // 是否需要除千操作
        balance = realConversion ? balance.multiply(new BigDecimal(1000)) : balance;
        // 汇率转换成USD
        balance = balance.multiply(usd);
        // 原来除千的继续除千
        return realConversion ? balance.divide(new BigDecimal(1000), BigDecimal.ROUND_CEILING).setScale(6) : balance;
    }

    /**
     * 获取美元汇率
     * @param currency
     * @return
     */
    private BigDecimal getUsd(String currency) {
        Object o = redisUtils.get(currency);
        if (ObjectUtils.isEmpty(o)){
            currency = currency.startsWith("K") || currency.startsWith("k") ? currency.substring(1) : currency;
            ResponseEntity<String> response = httpUtil.doGet("https://api.exchangerate-api.com/v4/latest/" + currency);
            JSONObject jsonObject = JSON.parseObject(response.getBody());
            // 就是原来的货币换多少美元
            BigDecimal usd = new BigDecimal(jsonObject.getJSONObject("rates").getString("USD"));
            // 存入redis
            redisUtils.set(currency, usd, 3600);
            return usd;
        }
        BigDecimal usd = new BigDecimal(o.toString());
        return usd;
    }

    /**
     * 获取商户信息和钱包信息
     * @param currency
     * @return
     */
    private List<MerchantSummary> getSummaryResps(String currency) {
        JoinLambdaWrapper<SysMerchantInfo> lambdaWrapper = new JoinLambdaWrapper<SysMerchantInfo>(SysMerchantInfo.class);
        lambdaWrapper.eq(SysMerchantInfo::getCurrency, currency)
                .selectAs(merchant -> {
                    merchant.addFunAlias(SysMerchantInfo::getId, MerchantSummary::getMerchantId);
                    merchant.add(SysMerchantInfo::getName);
                    merchant.add(false, "isOthers", false);
                    merchant.add(false, "locale", false);
                });
        lambdaWrapper.leftJoin(SysMerchantWalletEntity.class, SysMerchantWalletEntity::getMerchantId, SysMerchantInfo::getId)
                .selectAs(wallet -> {
                    wallet.add(SysMerchantWalletEntity::getAvailableBalance);
                })
                .orderByDesc(SysMerchantWalletEntity::getAvailableBalance)
                .end();
        List<MerchantSummary> merchantSummaries = merchantInfoService.joinList(lambdaWrapper, MerchantSummary.class);
        return merchantSummaries;
    }
}
